﻿/*
Author : Onur "Xtro" ER
Create date : 09.05.2009
Last update : 04.01.2012
*/

using System.Collections.Generic;
using System.Drawing;
using System.Linq;

using Xtro.MDX;
using Xtro.MDX.Direct3D10;
using Xtro.MDX.Direct3DX10;
using Color = Xtro.MDX.Direct3DX10.Color;
using Font = Xtro.MDX.Direct3DX10.Font;
using Constants = Xtro.MDX.Direct3D10.Constants;
using D3D10Functions = Xtro.MDX.Direct3D10.Functions;
using D3DX10Functions = Xtro.MDX.Direct3DX10.Functions;

using Xtro.Gerecler;

namespace Xtro.DirectX.Direct3D
{
    public sealed class TContext
    {
        internal int FAdapterNo;
        CreateDeviceFlag FFlags;
        internal readonly List<TScreen> Screens = new List<TScreen>();
        internal readonly List<TTexture> Textures = new List<TTexture>();
        public string Name;

        Font Font;
        Sprite Sprite;

        public bool Active { get { return Device != null; } }

        public Device Device { get; private set; }

        public int AdapterNo
        {
            get { return FAdapterNo; }
            set
            {
                if (value < 0 || value >= Manager.FAdapters.Count) throw new EInvalidParameter("Size", value.GetType(), string.Format(Gerecler.ResourcesReason.OutOfBounds, value.ToString()), Name);

                if (Device != null) throw new EInvalidCall(Name, GetType(), ResourcesReason.NotInactive, Name);

                FAdapterNo = value;
            }
        }

        public CreateDeviceFlag Flags
        {
            get { return FFlags; }
            set
            {
                if (Device != null) throw new EInvalidCall(Name, GetType(), ResourcesReason.NotInactive, Name);

                FFlags = value;
            }
        }

        public TContext(string Name)
        {
            this.Name = Name;

            if (Manager.Factory == null) throw new EInvalidCall(Manager.Type.Name, Manager.Type, ResourcesReason.NotActive, Name);

            FontDescription = new FontDescription
            {
                CharacterSet = FontCharacterSet.Default,
                FaceName = "Microsoft Sans Serif",
                Height = 14,
                Width = 0,
                Italic = false,
                MipLevels = 1,
                PitchAndFamily = FontPitchAndFamilyFlag.Default,
                OutputPrecision = FontPrecision.Default,
                Quality = FontQuality.Default,
                Weight = FontWeight.Normal
            };

            Manager.Contexts.Add(this);
        }

        public FontDescription FontDescription { get; private set; }

        public void SetFont(ref FontDescription Description)
        {
            if (FontDescription == Description) return;
            if (Device != null)
            {
                try { CreateFont(ref Description); }
                catch
                {
                    try
                    {
                        var OldDescription = FontDescription;
                        CreateFont(ref OldDescription);
                    }
                    catch { }

                    throw;
                }
            }

            FontDescription = Description;
        }

        public void Start()
        {
            if (Device != null) throw new EInvalidCall(Name, GetType(), ResourcesReason.NotInactive, Name);
            if (Manager.Factory == null) throw new EInvalidCall(Manager.Type.Name, Manager.Type, ResourcesReason.NotActive, Name);

            if (Starting != null) Starting(this);

            Device Out;
            var Result = D3D10Functions.CreateDevice(Manager.FAdapters[FAdapterNo].Adapter, DriverType.Hardware, null, FFlags, Constants.SdkVersion, out Out);
            if (Result == (int)Windows.EError.Fail && (FFlags & CreateDeviceFlag.Debug) > 0) throw new ENotAvailable("Direct3D 10 SDK", Name);
            if (Result != 0) throw new EDirectX("CreateDevice", Result, Name);
            Device = Out;

            try
            {
                var Description = FontDescription;
                CreateFont(ref Description);

                Result = D3DX10Functions.CreateSprite(Device, 0, out Sprite);
                if (Result == (int)Windows.EError.OutOfMemory) throw new ENotAvailable(ResourcesCommon.RequiredResourceMemory, Name);
                if (Result != 0) throw new EDirectX("CreateSprite", Result, Name);

                foreach (var Screen in Screens)
                {
                    Screen.Start();
                }
            }
            catch
            {
                var Stopping = this.Stopping;
                var Stopped = this.Stopped;

                this.Stopping = null;
                this.Stopped = null;
                try { Stop(); }
                finally
                {
                    this.Stopping = Stopping;
                    this.Stopped = Stopped;
                }

                throw;
            }

            if (Started != null) Started(this, Device);
        }

        void CreateFont(ref FontDescription FontDescription)
        {
            DeleteFont();

            var Result = D3DX10Functions.CreateFontIndirect(Device, ref FontDescription, out Font);
            if (Result == (int)Windows.EError.OutOfMemory) throw new ENotAvailable(ResourcesCommon.RequiredResourceMemory, Name);
            if (Result != 0) throw new EDirectX("CreateFontIndirect", Result, Name);
        }

        void DeleteFont()
        {
            if (Font == null) return;
            
            Font.Release();
            Font = null;
        }

        public void Stop()
        {
            if (Device == null) throw new EInvalidCall(Name, GetType(), ResourcesReason.NotActive, Name);

            if (Stopping != null) Stopping(this, Device);

            if (BegunDrawText) EndDrawText();

            foreach (var Screen in Screens)
            {
                Screen.Stop();
            }
            foreach (var Texture in Textures.Where(Texture => Texture.Resource != null))
            {
                Texture.Stop();
            }

            DeleteFont();

            if (Sprite != null)
            {
                Sprite.Release();
                Sprite = null;
            }

            Device.Release();
            Device = null;

            if (Stopped != null) Stopped(this);
        }

        public delegate void OEvent(TContext Sender);

        public delegate void ORender(TContext Sender, List<TScreen> Screens);

        public delegate void OStarted(TContext Sender, Device Device);
        public delegate void OStopping(TContext Sender, Device Device);

        public event ORender Render;
        public event OEvent Starting;
        public event OStarted Started;
        public event OStopping Stopping;
        public event OEvent Stopped;

        public void PerformRender(params TScreen[] Screens)
        {
            if (Device == null) throw new EInvalidCall(Name, GetType(), ResourcesReason.NotActive, Name);

            if (Render == null) throw new EInvalidCall(Name, GetType(), ResourcesReason.RenderEventIsNotAssigned, Name);

            var ScreensToBeRendered = new List<TScreen>();

            foreach (var Screen in Screens.Where(Screen => Screen != null && Screen.Context != null && Screen.TestReady()))
            {
                Screen.Clear();
                ScreensToBeRendered.Add(Screen);
            }

            Render(this, ScreensToBeRendered);

            foreach (var Screen in ScreensToBeRendered)
            {
                Screen.Present();
            }
        }

        bool BegunDrawText;

        bool DepthStencilStateSaved;
        DepthStencilState OldDepthStencilState;
        uint OldStencilReference;

        public void BeginDrawText(bool SaveDepthStencilState = false, bool SaveSpriteState = false)
        {
            if (Device == null) throw new EInvalidCall(Name, GetType(), ResourcesReason.NotActive, Name);

            if (BegunDrawText) throw new EInvalidCall(Name, GetType(), ResourcesReason.TextDrawingHasAlreadyBegun, Name);

            if (SaveDepthStencilState)
            {
                Device.OM_GetDepthStencilState(out OldDepthStencilState, out OldStencilReference);
                DepthStencilStateSaved = true;
            }
            else DepthStencilStateSaved = false;

            SpriteFlag SpriteFlags = 0;
            if (SaveSpriteState) SpriteFlags |= SpriteFlag.SaveState;

            var Result = Sprite.Begin(SpriteFlags);
            if (Result != 0) throw new EDirectX("Sprite.Begin", Result, Name);

            BegunDrawText = true;
        }

        public void EndDrawText()
        {
            if (Device == null) throw new EInvalidCall(Name, GetType(), ResourcesReason.NotActive, Name);

            if (!BegunDrawText) throw new EInvalidCall(Name, GetType(), ResourcesReason.TextDrawingHasNotBegun, Name);

            var Result = Sprite.End();
            if (Result != 0) throw new EDirectX("Sprite.End", Result, Name);

            if (DepthStencilStateSaved) Device.OM_SetDepthStencilState(OldDepthStencilState, OldStencilReference);

            BegunDrawText = false;
        }

        public int DrawText(ref Rectangle TextBox, string Text, ref Color Color, FontDrawFlag Flags, bool SaveDepthStencilState = false, bool SaveSpriteState = false)
        {
            if (Device == null) throw new EInvalidCall(Name, GetType(), ResourcesReason.NotActive, Name);

            var BegunHere = false;
            if (!BegunDrawText)
            {
                BeginDrawText(SaveDepthStencilState, SaveSpriteState);
                BegunHere = true;
            }

            int Result;
            try
            {
                Result = Font.DrawText(Sprite, Text, Text.Length, ref TextBox, Flags, ref Color);
                if (Result == 0) throw new EDirectX("Font.DrawText", Result, Name);
            }
            finally
            {
                if (BegunHere) EndDrawText();
            }

            return Result;
        }
    }
}